1 # About this Document (Discouragement for Cheaters)
3 You need to read the whole thing. If you are writing a protocol, you have
4 to understand filters, chains, callbacks, the works. Filter authors can skip
5 the "Writing a Protocol" section, I suppose, but other than that, read it all.
6 Trust me, it's worth it. Also, if something doesn't make sense, or at any point
7 something becomes difficult that you feel probably shouldn't be, drop us a
8 line at zoidberg@bug-br.org.br – we're glad to help.
10 ## General Architecture - Introduction to Chains
12 When you create an account in E-mail preferences, it creates two chains:
13 one inbound, one outbound. Each chain consists of a number of stored references
14 to mail filters, the generic type of Mail Daemon add-on, and their settings,
15 in the form of a flattened `BMessage`. The chain also stores global chain meta
16 data, also in a flattened `BMessage`, and various auxiliary information like
17 the chain name, and whether it is an outbound or inbound chain. The
18 distinction is important only to (1) set the color of the status bar when the
19 chain is run and (2) identify to the daemon which chains to run when it is
20 asked to fetch or to send mail. Each chain is identified by a unique unsigned
21 32-bit integer, the chain ID.
23 ## Introduction to Filters
25 Every MDR add-on is conceptually a filter, and, programmatically, derived
26 from the `Mail::Filter` class (which is to be found in `MailAddon.h`). An MDR
27 filter is not the standard sort of e-mail filter (a sorter, etc.), but is
28 defined to be any sort of entity that modifies, parses, or otherwise cares
29 about an e-mail message in the process of it being sent (or received). Into
30 this very broad definition, it is possible to fit all the add-ons it is
31 possible (for us, at any rate, with our inadequate minds) to imagine:
32 protocols, standard e-mail filters, notification windows, message saving,
33 etc. In fact, the vast, vast majority of what happens when a message is
34 transferred happens not in the daemon itself, but in one of its many add-ons.
36 ## Introduction to ChainRunner and Callbacks
38 The `Mail::ChainRunner` class exists, as the name would imply, to run chains.
39 It is of great use to you, the MDR add-on author. It publishes a variety of
40 useful public routines (like `ShowError()`) that will be described in their
41 appropriate sections, and does a number of other things that will also be
42 described later. But it does do one thing that is of general importance and
43 interest, and as near in importance for you to understand as filters:
46 Callbacks are called at the completion (successful or otherwise) of some
47 aspect of chain execution.
49 At the moment of completion, the callback's Callback() routine is called
50 with the error code that completed whatever it is the callback was waiting
51 for (`B_OK` or one of the `B_MAIL_*` family in MailAddon.h generally indicate
52 successful completion), and the callback is then destroyed. Callbacks come
53 in three kinds: message, process, and chain, and are registered by the
54 Register*Callback() routines of ChainRunner. These types of callbacks are
55 called, respectively, at the termination of a message transfer, a block of
56 message transfers (e.g. after all new messages are fetched off the server),
57 or the chain (just before all the add-ons are to be destroyed). These are
58 useful for a whole variety of tasks, and are used, for example, in such
59 things as deleting messages after they are fetched in POP3.
61 ## How to Write a Filter
63 The `Mail::Filter` class has two important hooks (actually, it only has two
64 hooks, but they are quite important one): `InitCheck()` and `ProcessMailMessage()`.
65 `InitCheck()` corresponds to the standard Be API `InitCheck()` function:
66 after construction of your filter (which, for things like protocols,
67 may involve complicated things like connecting to a server), `InitCheck()`
68 is called. If something is wrong (say, you couldn't connect to the server),
69 return an appropriate error code, and, if out_message does not equal `NULL`,
70 set it to an appropriate human readable error message. If it does equal
71 `NULL`, it is suggested that you call ChainRunner's `ShowError()` routine
72 (see Error Reporting for more information). If `InitCheck()` returns an error,
73 construction of the chain stops, all filters are deleted, and `ChainRunner`
74 packs up and goes home.
76 After successful construction of all the filters in the chain,
77 `ProcessMailMessage()` is called for each message that passes through it.
78 It takes what looks, at first glance, like a bewildering array of arguments,
79 but they generally make sense and most filter applications don't need to use
82 * `BPositionIO** io_message`: This is where the message is to be written to
83 (or read from). Astute observers will note that it is a pointer to a
84 pointer, and will question either our sanity or my typing, depending
85 on their frames of mind and personalities, among other things. But
86 this aspect allows you to modify the argument in unexpected (and,
87 naturally, very useful) ways. IMAP and POP3 replace the argument with
88 their own reader that retrieves data from the server as it is requested.
89 The Outbox filter swaps the argument for a BFile pointing to the message
90 to be fetched. Note that if you do swap it, you become responsible for
91 the deletion of the old argument. If you don't, there will be memory
92 leaks and other untold havoc. (Further information on replacing
93 `io_message` is available under How to Write a Protocol)
95 * `BEntry *io_entry`: This tells you where, on disk, the contents of the
96 message are kept. Useful for debugging purposes and for moving it about,
97 although this last is not reccomended. For information on why not, see
98 `io_headers` and `io_folder` (the next two, for the lazy).
100 * `BMessage *io_headers`: This contains a list of various kinds of random
101 junk in addition to a list of the headers of the message (after it's been
102 through the Parser filter, which means it's blank for protocols, and full
103 of yummy data for everyone else). The headers are stored as strings, with
104 the key the header tag in whatever case it was in the message header (the
105 subject, for instance, can be found with `FindString("Subject")`). If you
106 modify these entries, they are written to disk in whatever form you leave
107 them. In addition, there are several MDR-added entries (the previously
108 mentioned "random junk"). These are the THREAD, NAME, SIZE, and DESTINATION
109 fields. THREAD is the message thread (the subject with, Re:, Fwd:, etc.
110 removed), NAME is the name of the sender (as displayed in Tracker in the
111 "Name" attribute), SIZE (stored as a size_t) is the complete message size
112 (in bytes), and DESTINATION, which may or may not have been added, is an
113 override value for where, on disk, the message should be stored. You can
114 add this to have the message be placed somewhere other than the user's
117 * `BPath *io_folder`: This defines the subfolder of the user's inbox to
118 which the message will be added, expressed relative to the inbox. IMAP
119 uses it for the folder structure (it sets it to the name of the IMAP folder),
120 and POP3 leaves it blank. If left blank, it will not be placed in a
123 * `const char *io_uid`: This is the unique id of the message, in some form
124 that makes sense to the protocol. Usually of no concern to any filter.
126 After processing the message, `ProcessMailMessage()` returns either `B_OK`,
127 a descriptive error code, or one of the constants at the top of `MailAddon.h`
128 (`B_MAIL_DISCARD`, `B_MAIL_END_FETCH`, or `B_MAIL_END_CHAIN`). `B_OK` causes
129 the message to continue down the chain, `B_MAIL_DISCARD` causes it to be
130 deleted from disk and from the server and terminates the processing of the
131 message, error codes terminates the processing of the message as well,
132 `B_MAIL_END_FETCH` terminates the fetching of all remaining messages in this
133 fetch block, and `B_MAIL_END_CHAIN` indicates a catastrophic error has
134 occurred that requires the chain to be destroyed and the connection closed.
136 ## Instantiating and Configuring the Filter
138 MDR uses three symbols in a filter, two of which are optional. They are
141 `instantiate_mailfilter`: This is called to instantiate a new copy of your
142 filter. It is passed a copy of the filter's settings and a pointer to the
143 calling `ChainRunner`.
145 `instantiate_config_panel`: This is passed a copy of your filter's settings
146 and the chain meta data. From it, you should return a BView with configuration
147 options. E-mail prefs will call `ResizeToPreferred()` on it after it is
148 instantiated. To save, the prefs app will call `Archive()`. The passed
149 `BMessage *` becomes your settings.
151 `descriptive_name`: This is passed the settings of the filter, and a
152 `char * buffer`. If this routine returns `B_OK`, the contents of the buffer
153 will replace the name of the add-on in E-mail prefs.
155 ## How to Write a Protocol
157 While it is possible to write a protocol using nothing but the
158 `Mail::Filter` hooks, this is the Bad Way™ to do it. Instead of forcing you
159 through that, we've created the spectacularly useful `Mail::Protocol` class
160 (found, unsurprisingly, in `MailProtocol.h`). `Mail::Protocol` has two hooks,
161 `GetMessage()` and `DeleteMessage()`, a few member items, and a number of
162 important conventions. The MDR side of a mail protocol is fairly simple and
163 easy to understand; the network side of things may not be, and the best we can
164 do there is wish you luck. But you (hopefully) won't be cursing MDR.
166 ### Part I: Starting the Connection (or, what to do in your constructor)
168 When your protocol is instantiated by `instantiate_mailfilter()`, you are
169 expected to initiate the connection. Information on this is contained in your
170 settings in a standard format, and can be written to your settings in that
171 format by `Mail::ProtocolConfigView`. The existance of this class makes your
172 life easy (you can return one from instantiate_config_panel and not worry
173 about configuration any further). The format is described at the end of this
174 section. After successfully establishing the connection, you are expected to
175 add the unique ids of every message on the server to the protected data member
176 unique_ids. This is a StringList, a special class we've created just for MDR.
177 It uses simple operators like `+=`, and shouldn't require too much work to
178 understand. The header is StringList.h, in the support subdirectory.
179 After adding all the unique ids, you need to tell ChainRunner to get the new
180 messages. You do this as follows:
184 manifest->NotHere(*unique_ids, &to_dl);
185 runner->GetMessages(&to_dl, maildrop_size);
188 where maildrop_size is the combined total length (in bytes) of all the
189 messages on the server. If you don't know this, or determining it would be
190 complicated, slow, awkward, or just plain annoying, you can pass `-1`, in
191 which case the status bar will advance by message count instead of transferred
194 ### Part II: Protocol Settings Format
197 server (string): The IP address or hostname of the server
199 port (int32): The port on the server to connect to, if the user has specified one
201 flavor (int32): The 0-based index of the protocol flavor the user has chosen. If you didn't give the
202 user a choice of flavors in ProtocolConfigView, you can ignore this with impunity.
204 username (string): The user name entered in config.
206 password & cpasswd (string): These give you the password, which may or may not have been stored
207 encrypted. Use this code to get the password in plain text (stored in the password variable):
209 const char *password = settings->FindString("password");
210 char *passwd = get_passwd(settings, "cpasswd");
214 auth_method (int32): The 0-based index of the authentication method the user has chosen. If you
215 didn't give the user a choice of methods in ProtocolConfigView, you can ignore this with
219 ### Part III: Fetching Messages (or, what to do in GetMessage())
221 In your protocol's `GetMessage()` routine, you fetch the message indicated by
222 uid, into out_file. If your protocol is of the type that has multiple folders,
223 you can indicate that to future filters by setting out_folder_location to the
224 name of the folder in which the message is found. That's all you need to do.
226 Things get more complicated (you knew they would) if you want to support
227 partial message downloading. To do this, you need to replace out_file with
228 some sort of `BPositionIO` derivative that reads the message as required. Every
229 byte read from your `BPositionIO` derivative must also be written in the on-disk
230 representation of the message, that is, the old out_file argument. Second,
231 when your sub-class is deleted, you must delete the old out_file. Third, when
232 anyone does a Seek() operation referenced from SEEK_END, you must download the
233 whole message. You also need to add to out_headers an int32 named SIZE
234 containing the size, in bytes, of the complete message.
236 ### Part IV: Deleting Messages
238 This is really simple. When `DeleteMessage()` is called, you delete the
239 message indicated by uid. You also need to modify the unique_ids list. To do
240 this, just do `(*unique_ids) -= uid;`.
242 ### Part V: The Rest of It
244 As far as MDR is concerned, there is no rest of it. Everything else on the
245 BeOS side of things is taken care of by Mail::Protocol. Then there's the
246 network.... we'll leave you to that, and bother you no further, except to
247 ask you to read the next two sections:
249 ### RemoteStorageProtocol
251 For IMAP-like protocols (that is, remotely stored mail systems with multiple
252 mailboxes), we provide you with the RemoteStorageProtocol class. It handles
253 most everything on the BeOS side. You need simply to implement RSP's six hook
254 functions: GetMessage(), AddMessage(), DeleteMessage(), CopyMessage(),
255 CreateMailbox(), and DeleteMailbox(). These should be fairly self-explanatory.
256 A couple of notes are in order, however.
258 First, unique ids MUST NOT contain a '/' character. If they do, everything
261 Second, when you fill the unique_ids structure, use the following format:
262 `mailbox/id`. Mailbox names can contain / characters, and foo/bar will be
263 interpreted as a nested directory.
265 Second, you needn't remove anything from unique_ids in DeleteMessage().
266 That's handled for you.
268 Third, hooks like CopyMessage() and AddMessage() are passed the unique id
269 in a BString pointer. Fill this with the unique id the copy/uploaded message
270 receives once it's on the server.
272 ### Progress Reporting
274 When your protocol receives a message, or a part of it, it is important
275 (from the user's point of view) that you display that fact. ChainRunner
276 provides a very simple way to do this, in its ReportProgress() function.
277 ReportProgress() takes three arguments: the number of bytes received since the
278 last call, the number of messages received since the last call, and an update
279 message. If you just want to inform the user of something happening
280 ("Logging in", for instance), you can leave the bytes and messages argument
285 To report an error to the user, you need to call ChainRunner's `ShowError()`
286 method with some human-readable string describing the error condition. In
287 addition, you should take whatever action is necessary to report the error to
288 MDR in machine-understandable form, such as returning an error code, or calling
289 ChainRunner's Stop() method. Note that Stop() adds a message to the end of the
290 queue – you need to return a fatal error from ProcessMailMessage() to interrupt
291 a mail fetch in progress.